package com.skt.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.skt.domain.DTO.LoginDTO;
import com.skt.domain.DTO.request.ChangePasswordRequest;
import com.skt.domain.DTO.request.UpdateProfileRequest;
import com.skt.domain.ENUM.AccountStatus;
import com.skt.domain.PO.User;
import com.skt.domain.VO.UserVO;
import com.skt.exception.*;
import com.skt.mapper.UserMapper;
import com.skt.security.LoginUser;
import com.skt.service.UserService;
import com.skt.utils.JwtUtil;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.Valid;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

import static com.skt.utils.HtmlUtils.escapeHtml;

/**
 * 用户实现类
 */
// public class 类名 extends ServiceImpl<Mapper, PO> implements 接口类
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {


    private final BCryptPasswordEncoder passwordEncoder;

    private final JwtUtil jwtUtil;


    private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);

    // 在类顶部添加限流结构（单机版）
    private static final ConcurrentHashMap<Long, AtomicLong> PASSWORD_CHANGE_ATTEMPTS = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<Long, LocalDateTime> ATTEMPT_START_TIMES = new ConcurrentHashMap<>();

    // 1小时内最多尝试3次
    private static final int MAX_ATTEMPTS = 3;
    private static final Duration WINDOW_DURATION = Duration.ofHours(1);

    /**
     * 修改密码
     * @param request 修改密码请求
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void changePassword(ChangePasswordRequest request) {
        // === 2. 仅依赖用户ID识别身份 ===
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || !authentication.isAuthenticated()) {
            throw new RuntimeException("用户未登录");
        }

        Object principal = authentication.getPrincipal();
        Long userId = null;
        if (principal instanceof LoginUser) {
            userId = ((LoginUser) principal).getUserId();
        } else {
            throw new RuntimeException("无效用户凭证");
        }
        if (userId == null) {
            throw new RuntimeException("无效用户凭证");
        }

        // === 8. 限制操作频率（单机内存限流）===
        LocalDateTime now = LocalDateTime.now();
        AtomicLong attempts = PASSWORD_CHANGE_ATTEMPTS.computeIfAbsent(userId, k -> new AtomicLong(0));
        LocalDateTime startTime = ATTEMPT_START_TIMES.computeIfAbsent(userId, k -> now);

        // 重置窗口
        if (Duration.between(startTime, now).compareTo(WINDOW_DURATION) > 0) {
            attempts.set(0);
            ATTEMPT_START_TIMES.put(userId, now);
        }

        long currentAttempts = attempts.incrementAndGet();
        if (currentAttempts > MAX_ATTEMPTS) {
            // === 6. 返回信息不得泄露账户状态 ===
            throw new RuntimeException("操作失败，请检查输入信息");
        }

        // === 3. 校验用户当前状态 ===
        User user = this.baseMapper.selectById(userId);
        if (user == null || user.getAccountStatus() == null || user.getAccountStatus().getCode() != 1) {
            throw new RuntimeException("操作失败，请检查输入信息");
        }

        // === 1. 必须验证旧密码 ===
        if (!passwordEncoder.matches(request.getOldPassword(), user.getPassword())) {
            throw new RuntimeException("操作失败，请检查输入信息");
        }

        // === 4. 强制新密码强度策略 ===
        String newPassword = request.getNewPassword();
        if (newPassword == null || newPassword.length() < 8) {
            throw new RuntimeException("操作失败，请检查输入信息");
        }
        // 可扩展：正则校验复杂度（大小写、数字、特殊字符）

        // 新密码不能与旧密码相同（明文比对已通过，但再校验一次密文）
        if (passwordEncoder.matches(newPassword, user.getPassword())) {
            throw new RuntimeException("操作失败，请检查输入信息");
        }

        // 确认密码一致
        if (!newPassword.equals(request.getConfirmPassword())) {
            throw new RuntimeException("操作失败，请检查输入信息");
        }

        // === 7. 禁止记录密码相关数据 ===
        // 不记录 request 到日志（已做到：方法内无 log.info(request)）

        // === 9. 使用事务与并发控制 ===
        // @Transactional 已保证原子性；MyBatis-Plus updateById 为单行更新，天然避免并发覆盖

        // 执行密码更新
        String encodedNewPassword = passwordEncoder.encode(newPassword);
        user.setPassword(encodedNewPassword);
        user.setUpdatedAt(LocalDateTime.now());

        int updated = this.baseMapper.updateById(user);
        if (updated == 0) {
            throw new RuntimeException("操作失败，请检查输入信息");
        }

        // === 5. 修改后强制登出所有会话 ===
        // ⚠️ 注意：JWT 无状态，服务端无法使旧 Token 失效。
        // 建议：前端在收到成功响应后，主动清除 Token 并跳转登录页。
        // 若需服务端控制，请引入 Redis 黑名单，并调用：
        // jwtUtil.addToBlacklist(userId, jwtUtil.getTokenMaxAge());

        // 清除本次限流计数（可选：成功后重置）
        PASSWORD_CHANGE_ATTEMPTS.remove(userId);
        ATTEMPT_START_TIMES.remove(userId);

        // === 10. 区分修改密码与忘记密码流程 ===
        // 本方法仅用于已登录用户修改密码。
        // “忘记密码”应由独立接口（如 /api/auth/reset-password）处理，需验证码，不在此实现。
    }

    /**
     * 更新个人资料
     *
     * @param updateProfileRequest 更新请求参数
     * @return 更新后的用户视图对象
     */
    @Override
    public UserVO updateOwnProfile(@Valid UpdateProfileRequest updateProfileRequest) {
        // 1. 提取认证身份
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        // 2. 验证认证状态
        if (authentication == null || !authentication.isAuthenticated()) {
            throw new RuntimeException("用户未登录");
        }

        // 3. 加载当前用户实体
        Object principal = authentication.getPrincipal();
        Long userId = null;

        // 仅从 LoginUser 中提取 userId，不回退到 username（避免身份歧义）
        if (principal instanceof LoginUser) {
            userId = ((LoginUser) principal).getUserId();
        } else {
            // 非 LoginUser 类型视为无效认证
            throw new RuntimeException("无效用户凭证");
        }

        // 显式校验 userId 非空
        if (userId == null) {
            throw new RuntimeException("无效用户凭证");
        }

        // 通过用户 ID 精确查询用户实体
        User user = this.baseMapper.selectById(userId);
        if (user == null) {
            throw new RuntimeException("用户不存在");
        }

        // 4. 校验用户状态
        if (user.getAccountStatus() == null || user.getAccountStatus().getCode() != 1) {
            throw new RuntimeException("该用户已被禁用");
        }

        // 5. 绑定并校验输入数据（@Valid 已在 Controller 层处理）

        // 6. 执行业务唯一性与内容安全校验
        if (updateProfileRequest.getEmail() != null) {
            // 检查邮箱是否被其他用户占用（内联）
            if (this.baseMapper.selectCount(
                    new LambdaQueryWrapper<User>()
                            .eq(User::getEmail, updateProfileRequest.getEmail())
                            .ne(User::getId, userId)
            ) > 0) {
                throw new RuntimeException("该邮箱已被其他用户使用");
            }
        }

        if (updateProfileRequest.getPhone() != null) {
            // 检查手机号是否被其他用户占用（内联）
            if (this.baseMapper.selectCount(
                    new LambdaQueryWrapper<User>()
                            .eq(User::getPhone, updateProfileRequest.getPhone())
                            .ne(User::getId, userId)
            ) > 0) {
                throw new RuntimeException("该手机号已被其他用户使用");
            }
        }

        // 6.3 XSS 防护
        String realName = updateProfileRequest.getRealName();
        if (realName != null) {
            user.setRealName(escapeHtml(realName));
        }

        // 7. 受控字段级更新
        if (updateProfileRequest.getPhone() != null) {
            user.setPhone(updateProfileRequest.getPhone());
        }
        if (updateProfileRequest.getEmail() != null) {
            user.setEmail(updateProfileRequest.getEmail());
        }

        // 8. 持久化与并发控制
        int updated = this.baseMapper.updateById(user); // updated: 更新影响的行数
        if (updated == 0) {
            throw new RuntimeException( "信息已被他人修改，请刷新页面后重试");
        }

        // 9. 数据脱敏转换
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);

        // 10. 安全返回结果
        return userVO;
    }

    @Override
    public User getUserByUsername(String username) {
        if (username == null || username.isEmpty()) {
            return null;
        }
        return this.baseMapper.selectOne(
                new LambdaQueryWrapper<User>().eq(User::getUsername, username)
        );
    }

    /**
     * 检查邮箱是否被其他用户占用
     */
    private boolean isEmailOccupied(String email, Long currentUserId) {
        return this.baseMapper.selectCount(
                new LambdaQueryWrapper<User>()
                        .eq(User::getEmail, email)
                        .ne(User::getId, currentUserId)
        ) > 0;
    }

    /**
     * 检查手机号是否被其他用户占用
     */
    private boolean isPhoneOccupied(String phone, Long currentUserId) {
        return this.baseMapper.selectCount(
                new LambdaQueryWrapper<User>()
                        .eq(User::getPhone, phone)
                        .ne(User::getId, currentUserId)
        ) > 0;
    }

    /**
     * 获取当前用户信息
     *
     * @return 脱敏后的用户视图对象（UserVO）
     */
    @Override
    public UserVO getCurrentUser() {
        // 1. 提取认证身份
        // 从 Spring Security 上下文中获取当前请求的认证信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        // 2. 验证认证状态
        // 若未认证或认证无效，直接拒绝访问
        if (authentication == null || !authentication.isAuthenticated()) {
            throw new RuntimeException("未登录");
        }

        // 3. 加载当前用户实体
        // 从已认证的 Principal 中提取用户标识，并查询对应的用户持久化对象（PO）
        Object principal = authentication.getPrincipal();
        Long userId = null;
        String username = null;

        // 优先：从自定义 UserDetails（LoginUser）中获取结构化用户信息
        if (principal instanceof LoginUser) {
            userId = ((LoginUser) principal).getUserId();
            username = ((LoginUser) principal).getUsername();
        }
        // 兜底：处理仅包含用户名字符串的认证主体（如简单 Token 场景）
        else if (principal instanceof String) {
            username = (String) principal;
        }
        // 异常：无法识别的认证主体类型，说明认证流程异常
        else {
            throw new RuntimeException("无法获取用户身份标识");
        }

        // 根据标识查询用户实体（优先使用 ID，确保高效且唯一）
        User user = null;
        if (userId != null) {
            user = this.baseMapper.selectById(userId);
        } else if (username != null) {
            user = this.baseMapper.selectOne(
                    new LambdaQueryWrapper<User>().eq(User::getUsername, username)
            );
        }

        // 用户实体不存在：认证信息与数据状态不一致（如用户已被删除），视为安全异常
        if (user == null) {
            throw new RuntimeException("用户不存在");
        }

        // 4. 校验用户状态
        // 检查用户是否启用（status = 1 表示启用，0 表示禁用）
        if (user.getAccountStatus() == null || user.getAccountStatus().getCode() != 1) {
            throw new RuntimeException("用户账户已被禁用");
        }

        // 5. 数据脱敏转换
        // 将用户 PO 转换为对外暴露的 VO，剔除密码、密钥等敏感字段
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        // TODO: 可在此处添加额外脱敏逻辑（如手机号、邮箱部分隐藏）

        // 6. 安全返回结果
        // 所有校验通过，返回脱敏后的用户信息
        return userVO;
    }


    /**
     * 注册
     * @param loginDTO
     * @return
     */
    @Override
    public UserVO register(LoginDTO loginDTO) {
        // 详细日志记录接收到的DTO
        log.info("Received registration DTO: {}", loginDTO);

        // 1.参数校验
        String username = loginDTO.getUsername();
        String password = loginDTO.getPassword();
        String email = loginDTO.getEmail();
        String phone = loginDTO.getPhone();
        String realName = loginDTO.getRealName(); // 获取真实姓名

        // 详细日志
        log.info("Registration parameters - username: {}, email: {}, phone: {}, realName: {}",
                username, email, phone, realName);

        // 2.查重用户名,邮箱,手机号
        User userName = this.baseMapper.selectOne(Wrappers.lambdaQuery(User.class)
                .eq(User::getUsername, username));
        if (userName != null) throw new RuntimeException("该用户名已被使用");

        User userEmail = this.baseMapper.selectOne(Wrappers.lambdaQuery(User.class)
                .eq(User::getEmail, email));
        if (userEmail != null) throw new RuntimeException("该邮箱已被使用");

        User userPhone = this.baseMapper.selectOne(Wrappers.lambdaQuery(User.class)
                .eq(User::getPhone, phone));
        if (userPhone != null) throw new RuntimeException("该手机号已被使用");

        // 3.加密密码
        String encodedPassword = passwordEncoder.encode(password);
        log.info("Password encrypted");

        // 4.初始化用户状态
        User user = new User();
        BeanUtils.copyProperties(loginDTO, user);

        // 确保设置 realName 字段
        user.setRealName(realName);

        // 如果 realName 为空，使用用户名作为默认值
        if (realName == null || realName.trim().isEmpty()) {
            user.setRealName(username);
            log.warn("realName is empty, using username as default: {}", username);
        }

        user.setPassword(encodedPassword);
        user.setAccountStatus(AccountStatus.ENABLED);
        user.setCreatedAt(LocalDateTime.now());
        user.setUpdatedAt(LocalDateTime.now());

        // 详细日志记录要插入的用户对象
        log.info("User object to insert: {}", user);

        // 5.保存用户
        try {
            this.baseMapper.insert(user);
            log.info("User inserted successfully");
        } catch (Exception e) {
            log.error("Failed to insert user", e);
            throw e;
        }

        // 6.转VO
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);

        // 7.返回结果
        return userVO;
    }


    /**
     * 登录
     */
    @Override
    // TODO JWT 生成后续的Cookie
    public UserVO login(LoginDTO loginDTO) {
        // 1.参数校验
        String username = loginDTO.getUsername();
        String password = loginDTO.getPassword();

        if (StringUtils.isEmpty(username)) {
            throw ValidationException.of(ValidationErrorCode.USERNAME_EMPTY);

        }
        if (StringUtils.isEmpty(password)) {
            throw ValidationException.of(ValidationErrorCode.PASSWORD_EMPTY);
        }

        // 2.用户是否存在
        // SELECT * FROM user WHERE username = 'SKT' LIMIT 1;
        User user = this.baseMapper.selectOne(Wrappers.lambdaQuery(User.class)
                .eq(User::getUsername, username));
        if (user == null) {
            throw AuthException.of(AuthErrorCode.USERNAME_PASSWORD_ERROR);
        }

        // 3.判断用户状态
        AccountStatus accountStatus = user.getAccountStatus();
        if (accountStatus == null || accountStatus == AccountStatus.DISABLED) {
            throw AuthException.of(AuthErrorCode.ACCOUNT_DISABLED);
        }

        // 4.判断密码是否正确
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw AuthException.of(AuthErrorCode.USERNAME_PASSWORD_ERROR);
        }

        // 5.记录登录时间
        user.setUpdatedAt(LocalDateTime.now());
        this.baseMapper.updateById(user);

        // 6.生成 JWT Token - 建议使用用户ID而不是用户名（更安全）
        String token = jwtUtil.generateToken(user.getId().toString());

        // 7.转VO
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        userVO.setToken(token); // ✅ 将 Token 注入 VO

        // 8.返回结果
        return userVO;
    }
}